package cn.serein.charging.device.service.impl;

import cn.serein.charging.common.pojo.param.GunStatusUpdateParam;
import cn.serein.charging.common.pojo.vo.StationInfoVO;
import cn.serein.charging.device.constants.Constants;
import cn.serein.charging.device.pojo.StationPO;
import cn.serein.charging.device.service.DeviceService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;


import java.util.*;

@Slf4j
@Service
public class DeviceServiceImpl implements DeviceService {

//    private static final Logger log = LoggerFactory.getLogger(DeviceServiceImpl.class);
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisTemplate<String, StationPO> redisTemplateForStationPo;

    @Autowired
    private RedisTemplate<String,Object> redisTemplateHash;

    @Override
    public List<StationInfoVO> getNearStation(Double longitude, Double latitude, Double distance) {
        /**
         * TODO  使用redis的geo 数据结构实现
         */

        Circle geoCircle = getCircle(longitude, latitude, distance);

        //redisTemplate提供了好多opsForXX 来操作不同的数据类型
        //GeoOperations geoOperations = redisTemplate.opsForGeo();
        //redisTemplate提供了好多boundXXOps 来操作不同的数据类型,并且绑定了key
//        BoundGeoOperations stations = redisTemplate.boundGeoOps(Constants.REDIS_GEO_STATION_MEMBER);

        RedisGeoCommands.GeoRadiusCommandArgs args = getGeoRadiusCommandArgs();

        /**
         * todo  通过geo实现
         */
         //   return getInfoAndGEOByGEO(geoCircle, args);

        /**
         * todo  通过string实现
         */
        //return getGeoByGEOAndGetInfoByString(geoCircle,args);

        /**
         * TODO  通过redisHash实现
         * 通过小map 来获取站的信息
         */
        // return getGeoByGEOAndGetInfoByHash(geoCircle, args);

        /**
         * TODO  通过redisHash实现
         * 通过设置全局的key   大Map的key
         */

        return getGeoByGEOAndGetInfoByHashAll(geoCircle,args);
    }

    private List<StationInfoVO> getGeoByGEOAndGetInfoByHashAll(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        // 从redis中获取数据
        // 这里是从stations_geo里面拿
        GeoResults<RedisGeoCommands.GeoLocation<Integer>> results =
                getGeoByGEO(geoCircle, args);

        List<StationInfoVO> stationInfoVOS = new ArrayList<>();

        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> result : results) {

            RedisGeoCommands.GeoLocation<Integer> content = result.getContent();

            // 充电站的经纬度
            Point point = content.getPoint();
            log.debug("当前场站的位置坐标:{}", point);

            // todo 报错
            //java.lang.ClassCastException:
            // cn.serein.charging.device.pojo.StationPO cannot be cast to java.io.Serializable
            Integer stationId = content.getName();

            // 充电站和入参用户位置的距离
//            Distance resultDistance = result.getDistance();
            Double resultDistance = result.getDistance().getValue();
            log.debug("当前场站与用户的距离:{}", resultDistance);

            /*//GEO的结果 result 转为  StationInfoVO
            StationInfoVO stationInfoVO = new StationInfoVO();
            BeanUtils.copyProperties(stationPO, stationInfoVO);
            stationInfoVO.setDistance(resultDistance);*/

            // 切记 与初始化中设置的key保持一致
            String key = Constants.STATION_INFO_HASH_ALL_KEY;

            // 使用hashOptions操作hash
            HashOperations<String,String,StationPO> hashOperations
                    = redisTemplateHash.opsForHash();

            // 通过key和拿到的id 拿到对应的value
            StationPO stationPO = hashOperations.get(key, stationId.toString());
            log.debug("通过hashMapAll当前场站信息:{}", stationPO);

            StationInfoVO stationInfoVO = getStationInfoVO(stationPO,resultDistance);

            stationInfoVOS.add(stationInfoVO);
        }
        return stationInfoVOS;
    }

    private List<StationInfoVO> getGeoByGEOAndGetInfoByHash(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        // 从redis中获取数据
        // 这里是从stations_geo里面拿
        GeoResults<RedisGeoCommands.GeoLocation<Integer>> results =
                getGeoByGEO(geoCircle, args);

        List<StationInfoVO> stationInfoVOS = new ArrayList<>();

        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> result : results) {

            RedisGeoCommands.GeoLocation<Integer> content = result.getContent();

            // 充电站的经纬度
            Point point = content.getPoint();
            log.debug("当前场站的位置坐标:{}", point);

            // todo 报错
            //java.lang.ClassCastException:
            // cn.serein.charging.device.pojo.StationPO cannot be cast to java.io.Serializable
            Integer stationId = content.getName();

            // 充电站和入参用户位置的距离
//            Distance resultDistance = result.getDistance();
            Double resultDistance = result.getDistance().getValue();
            log.debug("当前场站与用户的距离:{}", resultDistance);

            /*//GEO的结果 result 转为  StationInfoVO
            StationInfoVO stationInfoVO = new StationInfoVO();
            BeanUtils.copyProperties(stationPO, stationInfoVO);
            stationInfoVO.setDistance(resultDistance);*/

            // 切记 与初始化中设置的key保持一致
            String key = Constants.STATION_INFO_HASH_KEY + stationId;

            // 使用hashOptions操作hash
            HashOperations<String,String,Object> hashOperations
                    = redisTemplateHash.opsForHash();

            // 单个获取
            // Object o = hashOperations.get(key, "stationName");
            // 一次获取全部  通过key去获取对应的小map  从map中获取前面存的站名
            Map<String,Object> map = hashOperations.entries(key);
            log.debug("通过map当前场站信息:{}", map);

            // 强转估计不成功
            // StationPO stationPO = (StationPO) map.get("stationName");
            StationPO stationPO = new StationPO();
            // 只需要拿到stationName
            stationPO.setStationName(String.valueOf(map.get("stationName")));

            StationInfoVO stationInfoVO = getStationInfoVO(stationPO,resultDistance);

            log.debug("当前场站与用户的距离信息:{}", stationInfoVO);


            stationInfoVOS.add(stationInfoVO);
        }
        return stationInfoVOS;
    }

    private List<StationInfoVO> getGeoByGEOAndGetInfoByString(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {

        GeoResults<RedisGeoCommands.GeoLocation<Integer>> results =
                getGeoByGEO(geoCircle, args);

        log.debug("RedisString查询结果:{}", results);

        // 思考  Redis里面的位置数据  怎么和数据库的位置数据配合使用  是否参考计规则的逻辑？
        // TODO   肯定不参考
        // 两个方案  参考懒加载
        // 不参考懒加载

        List<StationInfoVO> stationInfoVOS = new ArrayList<>();

        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> result : results) {

            RedisGeoCommands.GeoLocation<Integer> content = result.getContent();

            // 充电站的经纬度
            Point point = content.getPoint();
            log.debug("当前场站的位置坐标:{}", point);

            // todo 报错
            //java.lang.ClassCastException:
            // cn.serein.charging.device.pojo.StationPO cannot be cast to java.io.Serializable
            Integer stationId = content.getName();

            // 充电站和入参用户位置的距离
//            Distance resultDistance = result.getDistance();
            Double resultDistance = result.getDistance().getValue();
            log.debug("当前场站与用户的距离:{}", resultDistance);

            /*//GEO的结果 result 转为  StationInfoVO
            StationInfoVO stationInfoVO = new StationInfoVO();
            BeanUtils.copyProperties(stationPO, stationInfoVO);
            stationInfoVO.setDistance(resultDistance);*/
            
            // 设置key
            String key = Constants.STATION_INFO_KEY + stationId;
            
            // 使用valueOperations.get(key) 获取数据
            ValueOperations<String, StationPO> valueOperations =
                    redisTemplateForStationPo.opsForValue();
            log.debug("stationPO:{}", valueOperations.get(key));
            
            StationPO stationPO = valueOperations.get(key);
            
            StationInfoVO stationInfoVO = getStationInfoVO(stationPO,resultDistance);

            log.debug("当前场站与用户的距离信息:{}", stationInfoVO);

//            stationInfoVO.setStationId(99);

            stationInfoVOS.add(stationInfoVO);
        }


        return stationInfoVOS;


    }

    private  GeoResults<RedisGeoCommands.GeoLocation<Integer>> getGeoByGEO(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        // 替换为通过string的
        BoundGeoOperations stations = redisTemplate.boundGeoOps(Constants.REDIS_STRING_STATION_MEMBER);

        // RedisGEO查询
        GeoResults<RedisGeoCommands.GeoLocation<Integer>>
                results = stations.radius(geoCircle, args);
        return results;
    }

    private StationInfoVO getStationInfoVO(StationPO stationPO, Double resultDistance) {
        //GEO的结果 result 转为  StationInfoVO
        StationInfoVO stationInfoVO = new StationInfoVO();
        BeanUtils.copyProperties(stationPO,stationInfoVO);
        stationInfoVO.setDistance(resultDistance);
        return stationInfoVO;
    }

    private List<StationInfoVO> getInfoAndGEOByGEO(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        GeoResults<RedisGeoCommands.GeoLocation<StationPO>> results =
                getInfoAndGeoByGEO(geoCircle, args);

        log.debug("RedisGEO查询结果:{}", results);

        // 思考  Redis里面的位置数据  怎么和数据库的位置数据配合使用  是否参考计规则的逻辑？
        // TODO   肯定不参考
        // 两个方案  参考懒加载
        // 不参考懒加载

        List<StationInfoVO> stationInfoVOS = new ArrayList<>();

        for (GeoResult<RedisGeoCommands.GeoLocation<StationPO>> result : results) {

            RedisGeoCommands.GeoLocation<StationPO> content = result.getContent();

            // 充电站的经纬度
            Point point = content.getPoint();
            log.debug("当前场站的位置坐标:{}", point);

            // todo 报错
            //java.lang.ClassCastException:
            // cn.serein.charging.device.pojo.StationPO cannot be cast to java.io.Serializable
            StationPO stationPO = content.getName();

            // 充电站和入参用户位置的距离
//            Distance resultDistance = result.getDistance();
            Double resultDistance = result.getDistance().getValue();
            log.debug("当前场站与用户的距离:{}", resultDistance);

            //GEO的结果 result 转为  StationInfoVO
            StationInfoVO stationInfoVO = new StationInfoVO();
            BeanUtils.copyProperties(stationPO, stationInfoVO);
            stationInfoVO.setDistance(resultDistance);

            log.debug("当前场站与用户的距离信息:{}", stationInfoVO);

//            stationInfoVO.setStationId(99);

            stationInfoVOS.add(stationInfoVO);
        }


        return stationInfoVOS;
    }

    private GeoResults<RedisGeoCommands.GeoLocation<StationPO>> getInfoAndGeoByGEO(Circle geoCircle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        BoundGeoOperations stations =
                redisTemplate.boundGeoOps(Constants.REDIS_GEO_STATION_MEMBER);

        // RedisGEO查询
        GeoResults<RedisGeoCommands.GeoLocation<StationPO>>
                results = stations.radius(geoCircle, args);
        return results;
    }

    private RedisGeoCommands.GeoRadiusCommandArgs getGeoRadiusCommandArgs() {
        // 定义附近查询参数
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        // 设置参数，告诉RedisGEO查询的时候，返回距离，  距离是动态的  用户在不同的点查询  距离也会不一样
        args.includeDistance();
        // 设置参数，告诉RedisGEO查询的时候返回  空间坐标(Coordinates)  经纬度  为了方便地图绘制
        args.includeCoordinates();
        return args;
    }

    private Circle getCircle(Double longitude, Double latitude, Double distance) {
        // 通过用户的经纬度构建RedisGEO 的 Point(点) 用户的位置  一个点
        Point geoPoint = new Point(longitude, latitude);

        // 通过用户传入的查找距离,理解为圆的半径, 距离的单位 Metrics 开车 千米 步行 米
        Distance geoDistance = new Distance(distance, Metrics.KILOMETERS);

        // 通过点和半径   画圆
        Circle geoCircle = new Circle(geoPoint, geoDistance);
        return geoCircle;
    }

    @Override
    public StationInfoVO getStationInfo(Integer gunId) {
        StationInfoVO stationInfoVO = new StationInfoVO();
//        stationInfoVO.setStationId(99);
        return stationInfoVO;
    }

    @Override
    public Boolean updateGunStatus(GunStatusUpdateParam param) {
        return true;
    }
}
